﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SSharp.Core.DataTypes;
using SSharp.Core.Evaluator;
using System.IO;
using SSharp.Core.Reader;
using System.Reflection;

namespace SSharp.Core.Builtins.Macros {
	/// <summary>
	/// The default load macro loads and executes scheme files.
	/// </summary>
	public class Load : Macro {
		private static readonly Symbol beginSymbol = new Symbol("begin");

		public object Expand(IList<object> args) {
			IList<string> validated = Validate(args);
			List<object> result = new List<object>();

			foreach (string file in validated) {
				result.AddRange(LoadSingleParameter(file));
			}

			result.Add("OK");
	
			return new Cons(beginSymbol, List.Create(result));
		}

		/// <summary>
		/// Loads according to a single parameter, returning an enumeration of all the expressions that should be evaluated.
		/// Default implementation simply calls LoadFile.
		/// </summary>
		protected virtual IEnumerable<object> LoadSingleParameter(string file) {
			return LoadFile(file);
		}

		/// <summary>
		/// Loads the entire contents of a given file.
		/// Returns an enumeration with all the expressions in the file
		/// </summary>
		public static IEnumerable<object> LoadFile(string file) {
			using (Stream stream = File.OpenRead(file))
				return LoadFile(stream);
		}
		
		/// <summary>
		/// Loads the entire contents of a given stream.
		/// Returns an enumeration with all the expressions in the file
		/// </summary>
		public static IEnumerable<object> LoadFile(Stream stream) {
			using (SchemeReader reader = new SchemeReader(stream)) {
				return reader.ReadAll().ToList();
			}
		}

		/// <summary>
		/// Validates that the arglist is a list of strings, and does the casting
		/// </summary>
		private IList<string> Validate(IList<object> args) {
			return args.Select(ValidateArgument).ToList();
		}

		/// <summary>
		/// Validates a single argument, returning a string of the validated argument if it's okay and throwing an exception otherwise.
		/// 
		/// Default implementation returns strings as they are, or appends .ss to symbols.
		/// </summary>
		protected virtual string ValidateArgument(object arg) {
			if (arg != null) {
				if (arg is string) {
					return arg.ToString();
				} else if (arg is Symbol) {
					return arg.ToString() + ".ss";
				}
			}

			throw new SyntaxError("load expects string arguments only, given " + arg);
		}
	}

	/// <summary>
	/// load.net loads .net assemblies, and makes all their types available.
	/// </summary>
	public class LoadDotNet : Load {
		/// <summary>
		/// Loads a single assembly. Since assemblies only contain types, no scheme definitions can occur here, so an empty enumeration is always returned.
		/// </summary>
		protected override IEnumerable<object> LoadSingleParameter(string name) {
			Assembly assy = Assembly.LoadWithPartialName(name);
			if (assy == null) {
				throw new FileLoadException("Could not load assembly " + name);
			}
			return new object[0];
		}

		/// <summary>
		/// No silly .ss on assembly names - simply return the string or symbol as given.
		/// </summary>
		protected override string ValidateArgument(object arg) {
			if (arg != null) {
				if (arg is string || arg is Symbol) {
					return arg.ToString();
				}
			}

			throw new SyntaxError("load.net expects string arguments only, given " + arg);
		}
	}
}
